﻿using CleanArchitecture.Core.Configuration;
using CleanArchitecture.Core.Handlers;
using CleanArchitecture.Core.Interfaces;
using CleanArchitecture.Infrastructure.Entities.AdminUsers;
using CleanArchitecture.Infrastructure.Interfaces;
using CleanArchitecture.Infrastructure.Interfaces.AdminUsers;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.AspNetCore.Mvc.Filters;
using System;
using System.Linq;

namespace CleanArchitecture.Web.Filters
{
    /// <summary>
    /// Represents a filter attribute that confirms access to the admin panel
    /// </summary>
    public class AuthorizeAdminAttribute : TypeFilterAttribute
    {
        #region Fields

        private readonly AuthorizeType _ignoreFilter;

        #endregion

        #region Ctor

        /// <summary>
        /// Create instance of the filter attribute
        /// </summary>
        /// <param name="ignore">Whether to ignore the execution of filter actions</param>
        public AuthorizeAdminAttribute(AuthorizeType ignore = AuthorizeType.Forbidden) : base(typeof(AuthorizeAdminFilter))
        {
            _ignoreFilter = ignore;
            Arguments = new object[] { ignore };
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets a value indicating whether to ignore the execution of filter actions
        /// </summary>
        public AuthorizeType IgnoreFilter => _ignoreFilter;

        #endregion


        #region Nested filter

        /// <summary>
        /// Represents a filter that confirms access to the admin panel
        /// </summary>
        private class AuthorizeAdminFilter : IAuthorizationFilter
        {
            #region Fields

            private readonly AuthorizeType _ignoreFilter;
            private readonly IWorkContext _workContext;
            private readonly bool _isDevelopment;
            private readonly IWebHelper _webHelper;

            #endregion

            #region Ctor

            public AuthorizeAdminFilter(AuthorizeType ignoreFilter,
                IWorkContext workContext,
                IWebHelper webHelper)
            {
                _ignoreFilter = ignoreFilter;
                _workContext = workContext;
                _isDevelopment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Development";
                _webHelper = webHelper;
            }

            #endregion

            #region Methods

            /// <summary>
            /// Called early in the filter pipeline to confirm request is authorized
            /// </summary>
            /// <param name="filterContext">Authorization filter context</param>
            public void OnAuthorization(AuthorizationFilterContext filterContext)
            {
                if (filterContext == null)
                    throw new ArgumentNullException(nameof(filterContext));


                AdminUser user = null;
                //check whether this filter has been overridden for the action
                //DavidLee 2019/11/23 验证后台授权的两种方式
                // 1： 在Controller下的action方法上指定Attribute: [AuthorizeAdmin(AuthorizeType.Forbidden)]
                //     AuthorizeType 默认为Forbidden ：登录用户才可以访问
                //                        Ignore : 在后台页面方法前使用[AuthorizeAdmin(AuthorizeType.Ignore)]，则任何用户可以访问，不进行授权拦截
                //                        AdminOnly: 只有登录用户角色为Admin（超级管理员）才能访问，否则回应403
                // 2:  在Controller下的action方法上指定Attribute: [AllowAnonymous]
                var actionFilter = filterContext.ActionDescriptor.FilterDescriptors
                    .Where(filterDescriptor => filterDescriptor.Scope == FilterScope.Action)
                    .Select(filterDescriptor => filterDescriptor.Filter).OfType<AuthorizeAdminAttribute>().FirstOrDefault();
                
                //ignore filter (the action is available even if a customer hasn't access to the admin area)
                if(actionFilter?.IgnoreFilter.GetType() == typeof(AuthorizeType))
                {
                    var filterArg = actionFilter?.IgnoreFilter ?? _ignoreFilter;
                    if (filterArg == AuthorizeType.Ignore && _isDevelopment)
                        return;
                    else if (filterArg == AuthorizeType.AdminOnly)
                    {
                        user = _workContext.CurrentAdminUser;
                        if (user == null || !_workContext.IsAdmin)
                        {
                            //filterContext.Result = new RedirectToRouteResult("AccessDenied");//new StatusCodeResult(403);
                            filterContext.Result = new RedirectToActionResult("AccessDenied", "Security", new { pageUrl = _webHelper.GetRawUrl(filterContext.HttpContext.Request) });
                            return;
                        }

                    }
                }

                if (filterContext.Filters.Any(filter => filter is AllowAnonymousFilter) && _isDevelopment)
                {
                    //如果用户方位的Action带有AllowAnonymousAttribute，则不进行授权验证
                    user = _workContext.CurrentAdminUser;
                    if (user == null) //伪造用户信息，绕过授权验证方便调试开发，但要确保数据库中有用户ID为1的用户数据
                        _workContext.CurrentAdminUser = EngineContext.Current.Resolve<IAdminUserService>().GetAdminUserById(1);
                    return;
                }
                //there is AdminAuthorizeFilter, so check access
                if (filterContext.Filters.Any(filter => filter is AuthorizeAdminFilter))
                {

                    //DaviLee 2019/11/23 权限授权验证只验证：
                    //  1：用户是否登录
                    //  2：用户角色是否为超级管理员
                    //  3: 用户角色如不为超级管理员则根据用户角色对配置的系统模块获取对应的访问菜单
                    user = _workContext.CurrentAdminUser;
                    if (user != null && _workContext.IsAdmin)
                    //增加这里目的是为后期后台运行TaskSchedule，防止后台(以管理员角色运行)运行任务被拦截
                        return;

                    if(user == null)
                        filterContext.Result = new ChallengeResult();
                }
            }

            #endregion
        }

        #endregion
    }
}
